package org.bagou.proj.plugin

import com.android.annotations.NonNull
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import javassist.ClassPool
import javassist.CtClass
import javassist.bytecode.ClassFile
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

class OkPatchPluginTransform extends Transform {

    @Override
    String getName() {
        return "OkPatchPluginTransform"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        // 表示接收到的输入数据类型
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        // 该transform 工作的作用域
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        // 是否增量变编译
        return false
    }

    private classPool = ClassPool.getDefault()
    OkPatchPluginTransform(Project project){
        // 将android.jar包添加到classPool中，一遍能直接找到android相关的类
        classPool.appendClassPath(project.android.bootClasspath[0].toString())

        classPool.importPackage("android.os.Bundle")
        classPool.importPackage("android.widget.Toast")
        classPool.importPackage("android.app.Activity")
        classPool.importPackage("android.util.Log")

    }


    @Override
    void transform(@NonNull TransformInvocation transformInvocation)
            throws TransformException, InterruptedException, IOException {
    // 向工程中所有Activity的onCreate方法中打桩插入一段代码
        // 对项目中参与编译的.class，以及jar中的.class都做插桩处理
        def outputProvider = transformInvocation.outputProvider

        transformInvocation.inputs.each {_inputs->
            // 对_inputs中directory目录下的class进行遍历
            _inputs.directoryInputs.each { directory->
                handleDirectoryInputs(directory.file)
                def dest = outputProvider.getContentLocation(
                        directory.name, directory.contentTypes,
                        directory.scopes, Format.DIRECTORY
                )

                FileUtils.copyDirectory(directory.file, dest)
            }

            // 对_inputs中jar包下的class进行遍历
            _inputs.jarInputs.each {jar->
                def jarOutputFile = handleJarInputs(jar.file)
                def jarName = jar.name
                def md5 = DigestUtils.md5Hex(jar.file.absolutePath)
                if (jarName.endsWith(".jar")) {
                    jarName = jarName.substring(0,jarName.length()-4)
                }
                def dest = outputProvider.getContentLocation(
                        md5+jarName,jar.contentTypes,
                        jar.scopes,Format.JAR
                )
                FileUtils.copyFile(jarOutputFile, dest)

            }
        }

        classPool.clearImportedPackages()
    }

    // 处理 directory目录下的class文件
    void handleDirectoryInputs(File fileDir) {
        // required: 添加file地址到classPool
        classPool.appendClassPath(fileDir.absolutePath)
        if (fileDir.isDirectory()) { // 如果fileDir是文件目录
            fileDir.eachFileRecurse {file->
                def filePath = file.absolutePath
                if (ifModifyNeed(filePath)) {//判断是否满足class修改条件
                    // 为兼容jar包下class修改共用方法modifyClass(**),将file转化为FileInputStream
                    FileInputStream fis = new FileInputStream(file)
                    def ctClass = modifyClass(fis)
                    ctClass.writeFile(fileDir.name) // 修改完成后再写回去
                    ctClass.detach()
                }
            }
        }
    }

    // 处理 jar包下的class文件
    File handleJarInputs(File file) {
        // required: 添加file地址到classPool
        classPool.appendClassPath(file.absolutePath)

        JarFile jarInputFile = new JarFile(file) // 经过JarFile转换后，可获取jar包中子文件
        def entryFiles = jarInputFile.entries()


        File jarOutputFile = new File(file.parentFile, "temp_"+file.name)
        if (jarOutputFile.exists()) jarOutputFile.delete()
        JarOutputStream jarOutputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarOutputFile)))

        while (entryFiles.hasMoreElements()) {
            def nextEle = entryFiles.nextElement()
            def nextEleName = nextEle.name

            def jarEntry = new JarEntry(nextEleName)
            jarOutputStream.putNextEntry(jarEntry)

            def jarInputStream = jarInputFile.getInputStream(nextEle)
            if (!ifModifyNeed(nextEleName)) {//判断是否满足class修改条件
               jarOutputStream.write(IOUtils.toByteArray(jarInputStream))
                jarInputStream.close()
                continue
            }
            println('before....handleJarInputs-modifyClass')
            CtClass ctClass = modifyClass(jarInputStream)
            def bytecode = ctClass.toBytecode()
            ctClass.detach()
            jarInputStream.close()

            jarOutputStream.write(bytecode)
            jarOutputStream.flush()

        }

        jarInputFile.close()
        jarOutputStream.closeEntry()
        jarOutputStream.flush()
        jarOutputStream.close()
        return jarOutputFile

    }


    // class文件处理方法-共用
    CtClass modifyClass(InputStream fis) {
        // 通过输入流 获取 javassist 中的CtClass对象
        ClassFile classFile = new ClassFile(new DataInputStream(new BufferedInputStream(fis)))
        def ctClass = classPool.get(classFile.name)
        if (ctClass.isFrozen())ctClass.defrost()

        // 开始执行修改逻辑
        // onCreate方法的参数 override fun onCreate(savedInstanceState: Bundle?)
        def bundle = classPool.get("android.os.Bundle")//获取到onCreate方法参数
        println(bundle)
        CtClass[] params = Arrays.asList(bundle).toArray() // 转化为反射入参数组

        def method = ctClass.getDeclaredMethod("onCreate", params)
        def message = "字节码插桩内容："+classFile.name
        println('字节码插桩内容：'+message)
        method.insertBefore("android.widget.Toast.makeText(this, "+"\""+ message +"\""+", android.widget.Toast.LENGTH_SHORT).show();")//给每个方法的最后一行添加代码行
        method.insertAfter("Log.d(\"MainActivity\", \"override fun onCreate方法后......\");")//给每个方法的最后一行添加代码行
        return ctClass
    }

    boolean ifModifyNeed(String filePath) {
        return (
                filePath.contains("org/bagou/proj")
                        && filePath.endsWith("Activity.class")
                        && !filePath.contains("R.class")
                        && !filePath.contains('$')
                        && !filePath.contains('R$')
                        && !filePath.contains("BuildConfig.class")
        )
    }

}